import os
import platform
import tkinter
from tkinter import *
from tkinter import ttk
from HCNetSDK import *
from PlayCtrl import *
from datetime import datetime
from tkinter import messagebox
# //这个例子改改可以做为交付版本。基本上要的功能都有了，抓图部分有些出入

# 登录的设备信息
WINDOWS_FLAG = True
win = None  # 预览窗口
funcRealDataCallBack_V30 = None  # 实时预览回调函数，需要定义为全局的

PlayCtrl_Port = c_long(-1)  # 播放句柄
Playctrldll = None  # 播放库
FuncDecCB = None   # 播放库解码回调函数，需要定义为全局的

# 获取当前系统环境
img_path = "D:\\pic\\"  # 图像保存路径，好像中文就会出各种问题
# img_path = "D:\\出\\"

# 创建的窗体宽高
windows_width = 1200
windows_high = 800

if not os.path.exists(img_path):
    os.makedirs(img_path)

def GetPlatform():
    sysstr = platform.system()
    print('' + sysstr)
    if sysstr != "Windows":
        global WINDOWS_FLAG
        WINDOWS_FLAG = False

# 设置SDK初始化依赖库路径
def SetSDKInitCfg():
    # 设置HCNetSDKCom组件库和SSL库加载路径
    # print(os.getcwd())
    if WINDOWS_FLAG:
        strPath = os.getcwd().encode('gbk')
        sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
        sdk_ComPath.sPath = strPath
        Objdll.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
        Objdll.NET_DVR_SetSDKInitCfg(3, create_string_buffer(
            strPath + b'\libcrypto-1_1-x64.dll'))
        Objdll.NET_DVR_SetSDKInitCfg(
            4, create_string_buffer(strPath + b'\libssl-1_1-x64.dll'))
    else:
        strPath = os.getcwd().encode('utf-8')
        sdk_ComPath = NET_DVR_LOCAL_SDK_PATH()
        sdk_ComPath.sPath = strPath
        Objdll.NET_DVR_SetSDKInitCfg(2, byref(sdk_ComPath))
        Objdll.NET_DVR_SetSDKInitCfg(
            3, create_string_buffer(strPath + b'/libcrypto.so.1.1'))
        Objdll.NET_DVR_SetSDKInitCfg(
            4, create_string_buffer(strPath + b'/libssl.so.1.1'))


def LoginDev(sdk):

    # 用户注册设备
    # c++传递进去的是byte型数据，需要转成byte型传进去，否则会乱码
    # 登录参数，包括设备地址、登录用户、密码等
    struLoginInfo = NET_DVR_USER_LOGIN_INFO()
    struLoginInfo.bUseAsynLogin = 0  # 同步登录方式
    struLoginInfo.sDeviceAddress = bytes("192.168.110.7", "ascii")  # 设备IP地址
    struLoginInfo.wPort = 8000  # 设备服务端口
    struLoginInfo.sUserName = bytes("admin", "ascii")  # 设备登录用户名
    struLoginInfo.sPassword = bytes("34er3434", "ascii")  # 设备登录密码
    struLoginInfo.byLoginMode = 0
    struDeviceInfoV40 = NET_DVR_DEVICEINFO_V40()
    UserID = sdk.NET_DVR_Login_V40(
        byref(struLoginInfo), byref(struDeviceInfoV40))

    return UserID, struDeviceInfoV40


def DecCBFun(nPort, pBuf, nSize, pFrameInfo, nUser, nReserved2):
    # 解码回调函数
    if pFrameInfo.contents.nType == 3:
        # 解码返回视频YUV数据，将YUV数据转成jpg图片保存到本地
        # 如果有耗时处理，需要将解码数据拷贝到回调函数外面的其他线程里面处理，避免阻塞回调导致解码丢帧
        # sFileName = ('pic/test_stamp[%d].jpg' %pFrameInfo.contents.nStamp) #这里路径错了
        sFileName = 'pic/test.jpg'
        nWidth = pFrameInfo.contents.nWidth
        nHeight = pFrameInfo.contents.nHeight
        nType = pFrameInfo.contents.nType
        dwFrameNum = pFrameInfo.contents.dwFrameNum
        nStamp = pFrameInfo.contents.nStamp
        # print("图像参数", nWidth, nHeight, nType, dwFrameNum, nStamp, sFileName)

        # 对抓拍功能没有什么用
        '''
        lRet = Playctrldll.PlayM4_ConvertToJpegFile(
            pBuf, nSize, nWidth, nHeight, nType, c_char_p(sFileName.encode()))
        if lRet == 0:
            print('PlayM4_ConvertToJpegFile fail, error code is:',
                  Playctrldll.PlayM4_GetLastError(nPort))
        else:
            print('PlayM4_ConvertToJpegFile success')  
        '''

def RealDataCallBack_V30(lPlayHandle, dwDataType, pBuffer, dwBufSize, pUser):     # 码流回调函数
    if dwDataType == NET_DVR_SYSHEAD:
        # 设置流播放模式
        Playctrldll.PlayM4_SetStreamOpenMode(PlayCtrl_Port, 0)
        # 打开码流，送入40字节系统头数据
        if Playctrldll.PlayM4_OpenStream(PlayCtrl_Port, pBuffer, dwBufSize, 1024*1024):
            # 设置解码回调，可以返回解码后YUV视频数据
            global FuncDecCB
            FuncDecCB = DECCBFUNWIN(DecCBFun)
            Playctrldll.PlayM4_SetDecCallBackExMend(
                PlayCtrl_Port, FuncDecCB, None, 0, None)
            # 开始解码播放
            if Playctrldll.PlayM4_Play(PlayCtrl_Port, cv.winfo_id()):
                print(u'播放库播放成功')
            else:
                print(u'播放库播放失败')
        else:
            print(u'播放库打开流失败')
    elif dwDataType == NET_DVR_STREAMDATA:
        Playctrldll.PlayM4_InputData(PlayCtrl_Port, pBuffer, dwBufSize)
    else:
        print(u'其他数据,长度:', dwBufSize)


def OpenPreview(Objdll, lUserId, callbackFun):  # 打开预览

    preview_info = NET_DVR_PREVIEWINFO()
    preview_info.hPlayWnd = 0
    preview_info.lChannel = 1  # 通道号lk在
    preview_info.dwStreamType = 0  # 主码流
    preview_info.dwLinkMode = 0  # TCP
    preview_info.bBlocked = 1  # 阻塞取流
    preview_info.dwDisplayBufNum = 15  # 缓冲区大小，15*1024*1024

    # 开始预览并且设置回调函数回调获取实时流数据
    lRealPlayHandle = Objdll.NET_DVR_RealPlay_V40(
        lUserId, byref(preview_info), callbackFun, None)
    return lRealPlayHandle


def InputData(fileMp4, Playctrldll):
    while True:
        pFileData = fileMp4.read(4096)
        if pFileData is None:
            break

        if not Playctrldll.PlayM4_InputData(PlayCtrl_Port, pFileData, len(pFileData)):
            break


def click_capture():  # SJBD00001 这里恐怕要大改,抓图后要存到自己指定的目录
    # print("clicked capture button")
    now = datetime.now()  # 获取当前时间（格式：2002-06-12T15:03:10.423Z） 
    filename = now.strftime("%Y-%m-%d %H-%M-%S") + ".jpg"
    img_file = img_path + filename
    Objdll.NET_DVR_SetCapturePictureMode(1)  # 抓取JPG图片，0模式为BMP图盘
    pFileName = ctypes.c_char_p()
    pFileName.value = bytes(img_file, "utf-8")  # 路径中包含中文就会放错位置，图片名乱码
    # pFileName.value = bytes(r"D:\pic\中文名字.jpg", "utf-8")
    print("pFileName.value =", pFileName.value)  # 打包时去除多余打印语句
    # 开始抓图
    res = Objdll.NET_DVR_CapturePicture(lRealPlayHandle, pFileName)
    if res:
        messagebox.showinfo('恭喜', '保存成功!')
        os.startfile(img_path)  # 打开存放图片的文件夹


if __name__ == '__main__':
    # 创建窗口
    win = tkinter.Tk()
    # 固定窗口大小
    win.resizable(0, 0)
    # win.overrideredirect(True)
    win.title("龙顺车辆抓拍")

    sw = win.winfo_screenwidth()   # 得到屏幕宽度
    sh = win.winfo_screenheight()  # 得到屏幕高度
    # print("屏幕宽度=", sw)
    # print("屏幕高度=", sh)
    # 窗口宽高
    ww = windows_width
    wh = windows_high
    x = (sw - ww) / 2
    y = (sh - wh) / 2
    win.geometry("%dx%d+%d+%d" % (ww, wh, x, y))
  
    # 创建一个Canvas，设置其背景色为白色
    cv = Canvas(
        win,
        width=1140,
        height=720,
        bg="white",
    )
    cv.place(x=30, y=15)

    # 创建左右两个Frame
    f_left = Frame(win)
    f_right = Frame(win)

    # 创建两个Button并分别放入左右两个Frame中
    button_left = Button(f_left, font=('楷体', 26), text="拍照并保存", bg='DodgerBlue', fg='white', width=10,
                         height=1, command=click_capture)
    button_left.pack(side=LEFT, fill="none", padx=30, pady=5)

    button_right = Button(f_right, font=('楷体', 26), text="关闭摄像头", bg='DodgerBlue', fg='white', width=10,
                          height=1, command=win.quit)
    button_right.pack(side=RIGHT, fill="none", padx=25, pady=5)

    # 将左右两个Frame放入窗体底部
    f_left.pack(anchor=SW, side=LEFT, fill=X, expand=True)  # side=LEFT
    f_right.pack(anchor=SE, side=RIGHT, fill=X, expand=True)

    # 加载库,先加载依赖库
    os.chdir(r'./lib/win')  # r前缀来禁用转义序列处理
    work_path = os.getcwd()
    Objdll = ctypes.CDLL(r'./HCNetSDK.dll')  # 加载网络库
    Playctrldll = ctypes.CDLL(r'./PlayCtrl.dll')  # 加载播放库
    # print("load dll successfully") 去除多余打印语句
    SetSDKInitCfg()  # 设置组件库和SSL库加载路径

    # 初始化DLL
    Objdll.NET_DVR_Init()
    # print("init device successfully ") 去除多余打印语句
    
    # 启用SDK写日志
    # Objdll.NET_DVR_SetLogToFile(
        # 3, bytes('./SdkLog_Python/', encoding="utf-8"), False)
    # print("config log to SdkLog_Python ")

    # 获取一个播放句柄
    if not Playctrldll.PlayM4_GetPort(byref(PlayCtrl_Port)):
        print(u'获取播放库句柄失败')

    # 登录设备 SJBD00001 无机时先注释掉，调一下界面效果

    (lUserId, device_info) = LoginDev(Objdll)   # 调用库函数加载设备信息和登录设备 （lUserId用户号，device_info设备,登录函数，一定要调通
    if lUserId < 0:
        err = Objdll.NET_DVR_GetLastError()
        print('Login device fail, error code is: %d' %
              Objdll.NET_DVR_GetLastError())
        # 释放资源
        Objdll.NET_DVR_Cleanup()
        exit()
    # print("登录成功 ")  打包时去除多余打印语句

    # 定义码流回调函数
    funcRealDataCallBack_V30 = REALDATACALLBACK(RealDataCallBack_V30)

    # 开启预览
    lRealPlayHandle = OpenPreview(Objdll, lUserId, funcRealDataCallBack_V30)
    if lRealPlayHandle < 0:
        print('Open preview fail, error code is: %d' %
              Objdll.NET_DVR_GetLastError())
        # 登出设备
        Objdll.NET_DVR_Logout(lUserId)
        # 释放资源
        Objdll.NET_DVR_Cleanup()
        exit()

    # show Windows
    win.mainloop()

    # 关闭预览
    Objdll.NET_DVR_StopRealPlay(lRealPlayHandle)

    # 停止解码，释放播放库资源
    if PlayCtrl_Port.value > -1:
        Playctrldll.PlayM4_Stop(PlayCtrl_Port)
        Playctrldll.PlayM4_CloseStream(PlayCtrl_Port)
        Playctrldll.PlayM4_FreePort(PlayCtrl_Port)
        PlayCtrl_Port = c_long(-1)

    # 登出设备
    Objdll.NET_DVR_Logout(lUserId)

    # 释放资源
    Objdll.NET_DVR_Cleanup()


